﻿//Copyright (c) Microsoft Corporation
//
//All rights reserved. 
//
//Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
using Windows.Storage;
using FluentAssertions;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using TED.WindowsAzure.Unplugged.Mobile.RemoteStorage.AzureBlob.Storage;
using TED.WindowsAzure.Unplugged.Mobile.Sync.File;
using TED.WindowsAzure.Unplugged.Mobile.Sync.Folder;
using TED.WindowsAzure.Unplugged.Mobile.Tests.Helpers;

namespace TED.WindowsAzure.Unplugged.Mobile.Tests.IntegrationTests
{
    [TestClass]
    public class IntegrationTests : IntegrationTestBase
    {
        [TestInitialize]
        public void Setup()
        {
        }

        [TestCleanup]
        public async Task TearDown()
        {
            // Clean up blob storage
            await CloudBlobHelper.CleanUpBlob();

            // Clean up local FS
            await BinaryStorageHelper.CleanUp();
        }

        [TestMethod]
        public async Task Pull_Once()
        {
            await CloudBlobHelper.FillBlobStorage(3);

            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri,
                this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            var folder = await
                client.SyncContext.GetSyncFolderAsync(this.FolderName);

            await folder.PullAsync();

            await AssertLocalFiles(3);
        }

        [TestMethod]
        public async Task Pull_Twice()
        {
            await CloudBlobHelper.FillBlobStorage(1);

            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri,
                this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));
            var folder = await client.SyncContext
                .GetSyncFolderAsync(this.FolderName);

            await folder.PullAsync();
            await folder.PullAsync();

            // Assert
            await AssertLocalFiles(1);
        }

        [TestMethod]
        public async Task Pull_Push_NoChanges()
        {
            await CloudBlobHelper.FillBlobStorage(2);

            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));
            var container = await client.SyncContext.GetSyncFolderAsync(this.FolderName);

            await container.PullAsync();

            await container.PushAsync();

            // Assert
            await AssertLocalFiles(2);
            Assert.AreEqual(2, (await CloudBlobHelper.GetBlobCount()));
        }

        [TestMethod]
        public async Task Pull_Push_WithNewFile()
        {
            await CloudBlobHelper.FillBlobStorage(2);

            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));
            var container = await client.SyncContext
                .GetSyncFolderAsync(FolderName);

            await container.PullAsync();

            var file = await container.CreateFileAsync("2.txt");
            using (var s = new StreamWriter((await file.OpenWriteAsync()).AsStreamForWrite()))
            {
                s.Write(Ipsum.GetPhrase(20));
            }
            await file.CommitAsync();

            await container.PushAsync();

            // Assert
            Assert.AreEqual(3, (await CloudBlobHelper.GetBlobCount()));
            await AssertLocalFiles(3);
        }

        [TestMethod]
        public async Task Pull_EventsFiring()
        {
            await CloudBlobHelper.FillBlobStorage(3);

            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            var folder = await
                client.SyncContext.GetSyncFolderAsync(this.FolderName);

            int pullStarted = 0, pullCompleted = 0, pullProgress = 0, syncListBuilt = 0;

            folder.PullStarted += (s, e) =>
            {
                pullStarted++;
            };
            folder.PullCompleted += (s, e) =>
            {
                pullCompleted++;
            };
            folder.PullProgressChanged += (s, e) =>
            {
                pullProgress++;
                Assert.IsNotNull(e.RemoteFile);
                Assert.IsNotNull(e.LocalFile);
            };
            folder.SyncListBuilt += (s, e) =>
            {
                syncListBuilt++;
                Assert.AreEqual(3, e.SyncFiles.Count());
            };

            await folder.PullAsync();

            await AssertLocalFiles(3);

            Assert.AreEqual(1, pullStarted);
            Assert.AreEqual(3, pullProgress);
            Assert.AreEqual(1, syncListBuilt);
            Assert.AreEqual(1, pullCompleted);
        }

        [TestMethod]
        public async Task Push_EventsFiring()
        {
            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            var folder = await
                client.SyncContext.GetSyncFolderAsync(this.FolderName);

            int pushStarted = 0, pushCompleted = 0, pushProgress = 0, syncListBuilt = 0;

            folder.PushStarted += (s, e) =>
            {
                pushStarted++;
            };
            folder.PushCompleted += (s, e) =>
            {
                pushCompleted++;
            };
            folder.PushProgressChanged += (s, e) =>
            {
                pushProgress++;
                Assert.IsNotNull(e.RemoteFile);
                Assert.IsNotNull(e.LocalFile);
            };
            folder.SyncListBuilt += (s, e) =>
            {
                syncListBuilt++;
                Assert.AreEqual(2, e.SyncFiles.Count());
            };

            var file = await folder.CreateFileAsync("1.txt");
            using (var s = new StreamWriter((await file.OpenWriteAsync()).AsStreamForWrite()))
            {
                s.Write(Ipsum.GetPhrase(20));
            }
            await file.CommitAsync();

            file = await folder.CreateFileAsync("0.txt");
            using (var s = new StreamWriter((await file.OpenWriteAsync()).AsStreamForWrite()))
            {
                s.Write(Ipsum.GetPhrase(20));
            }
            await file.CommitAsync();

            await folder.PushAsync();

            Assert.AreEqual(1, pushStarted);
            Assert.AreEqual(2, pushProgress);
            Assert.AreEqual(1, syncListBuilt);
            Assert.AreEqual(1, pushCompleted);
        }

        [TestMethod]
        public async Task Pull_EventsNotFiring()
        {
            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            var folder2 = await
                client.SyncContext.GetSyncFolderAsync(this.FolderName);

            var folder = await
                client.SyncContext.GetSyncFolderAsync("foo");

            int pullStarted = 0, pullCompleted = 0, pullProgress = 0, syncListBuilt = 0;

            folder.PullStarted += (s, e) =>
            {
                pullStarted++;
            };
            folder.PullCompleted += (s, e) =>
            {
                pullCompleted++;
            };
            folder.PullProgressChanged += (s, e) =>
            {
                pullProgress++;
                Assert.IsNotNull(e.RemoteFile);
                Assert.IsNotNull(e.LocalFile);
            };
            folder.SyncListBuilt += (s, e) =>
            {
                syncListBuilt++;
                Assert.AreEqual(3, e.SyncFiles.Count());
            };

            await folder2.PullAsync();

            Assert.AreEqual(0, pullStarted);
            Assert.AreEqual(0, pullProgress);
            Assert.AreEqual(0, syncListBuilt);
            Assert.AreEqual(0, pullCompleted);
        }

        [TestMethod]
        public async Task CreateNewFile_DoNotPush()
        {
            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            Assert.AreEqual(0, TableHelper.GetCount());

            ISyncFolder folder = await client.SyncContext.GetSyncFolderAsync(FolderName);

            Assert.AreEqual(0, TableHelper.GetCount());

            ISyncFile file = await folder.CreateFileAsync("myfile.txt");

            using (var writer = new StreamWriter((await file.OpenWriteAsync()).AsStreamForWrite()))
            {
                writer.WriteLine("foo");
                writer.Flush();
            }
            await file.CommitAsync();

            // Assert
            Assert.AreEqual(1, TableHelper.GetCount());

            var dbFile = TableHelper.GetItems().First();
            Assert.AreEqual(SyncFileStatus.Created, dbFile.Status);
            Assert.AreEqual("myfile.txt", dbFile.FileName);

            Assert.AreEqual(1,
                await BinaryStorageHelper.GetCountAsync(FolderName));

            var localStorageFile =
              await BinaryStorageHelper.GetFileAsync(string.Format("{0}\\{1}", dbFile.FolderName, dbFile.FileName));
            Assert.IsNotNull(localStorageFile);
        }

        [TestMethod]
        public async Task CreateNewFile_EmptyStream()
        {
            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            Assert.AreEqual(0, TableHelper.GetCount());

            ISyncFolder folder = await client.SyncContext.GetSyncFolderAsync(FolderName);

            Assert.AreEqual(0, TableHelper.GetCount());

            ISyncFile file = await folder.CreateFileAsync("myfile.txt");

            Exception exception = null;

            try
            {
                await file.CommitAsync();
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            // Assert
            Assert.IsNotNull(exception);

            // Assert
            Assert.AreEqual(1, TableHelper.GetCount());

            var dbFile = TableHelper.GetItems().First();
            Assert.AreEqual(SyncFileStatus.Undefined, dbFile.Status);
            Assert.AreEqual("myfile.txt", dbFile.FileName);

            Assert.AreEqual(0,
                await BinaryStorageHelper.GetCountAsync(FolderName));

            var localStorageFile =
              await BinaryStorageHelper.GetFileAsync(string.Format("{0}\\{1}", dbFile.FolderName, dbFile.FileName));
            Assert.IsNull(localStorageFile);
        }

        [TestMethod]
        public async Task CreateNewFile_WriteTwice()
        {
            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            ISyncFolder folder = await client.SyncContext.GetSyncFolderAsync(FolderName);

            ISyncFile file = await folder.CreateFileAsync("myfile.txt");

            using (var writer = new StreamWriter((await file.OpenWriteAsync())
                .AsStreamForWrite()))
            {
                writer.WriteLine("foo");
                writer.Flush();
            }

            await file.CommitAsync();

            using (var writer = new StreamWriter((await file.OpenWriteAsync())
                .AsStreamForWrite()))
            {
                writer.WriteLine("foo2");
                writer.Flush();
            }

            // Assert
            Assert.AreEqual(1, TableHelper.GetCount());

            var dbFile = TableHelper.GetItems().First();
            Assert.AreEqual(SyncFileStatus.Undefined, dbFile.Status);
            Assert.AreEqual("myfile.txt", dbFile.FileName);

            Assert.AreEqual(1, await BinaryStorageHelper.GetCountAsync(FolderName));

            string path = string.Format("{0}\\{1}", dbFile.FolderName, dbFile.FileName);

            var localStorageFile =
              await BinaryStorageHelper.GetFileAsync(path);
            Assert.IsNotNull(localStorageFile);

            Assert.AreEqual("foo2\r\n",
                await BinaryStorageHelper.GetFileContentAsync(path));
        }

        [TestMethod]
        public async Task PushTwice_ChangeFileContentOnServer()
        {
            var client = new UnpluggedMobileServiceClient(this.MobileServicesUri, this.MobileServicesKey);

            await client.SyncContext.InitializeAsync(new WindowsAzureBlobStorage(client));

            ISyncFolder folder = await client.SyncContext.GetSyncFolderAsync(FolderName);

            ISyncFile file = await folder.CreateFileAsync("myfile.txt");

            using (var writer = new StreamWriter((await file.OpenWriteAsync())
                .AsStreamForWrite()))
            {
                writer.WriteLine("foo");
                writer.Flush();
            }

            await file.CommitAsync();

            // Assert
            Assert.AreEqual(1, TableHelper.GetCount());

            var dbFile = TableHelper.GetItems().First();
            Assert.AreEqual(SyncFileStatus.Created, dbFile.Status);
            Assert.AreEqual("myfile.txt", dbFile.FileName);

            Assert.AreEqual(1, await BinaryStorageHelper.GetCountAsync(FolderName));

            await folder.PushAsync();
            await folder.PullAsync();
            // check contents on server using native client
            StorageFile foofile = await CloudBlobHelper.DownloadSingleBlob("myfile.txt");

            string text = await FileIO.ReadTextAsync(foofile);
            text.Should().Be("foo\r\n");

            // Should this be AsServer now that it's been pulled from the server?
            Assert.AreEqual(SyncFileStatus.Created, dbFile.Status);

            using (var writer = new StreamWriter((await file.OpenWriteAsync())
                .AsStreamForWrite()))
            {
                writer.WriteLine("foo2");
                writer.Flush();
            }

            await file.CommitAsync();

            // Should this be Updated now that the file has been created and pulled from the server?
            Assert.AreEqual(SyncFileStatus.Created, dbFile.Status);

            await folder.PushAsync();
            await CloudBlobHelper.EmptyTestDirectory();

            // check contents on server
            StorageFile foo2file = await CloudBlobHelper.DownloadSingleBlob("myfile.txt");
            text = await FileIO.ReadTextAsync(foo2file);
            text.Should().Be("foo2\r\n");
        }

        private async Task AssertLocalFiles(int num)
        {
            Assert.AreEqual(num, TableHelper.GetCount());

            Assert.AreEqual(1, await BinaryStorageHelper.GetCountAsync());
            Assert.AreEqual(num, await BinaryStorageHelper.GetCountAsync(FolderName));

            for (int i = 0; i < num; i++)
            {
                var items = TableHelper.GetItems();
                var file = items.Where(f => f.FileName == string.Format("{0}.txt", i)).FirstOrDefault();

                Assert.IsNotNull(file);
                Assert.AreEqual(SyncFileStatus.AsServer, file.Status);

                var localStorageFile =
                    await BinaryStorageHelper.GetFileAsync(
                        string.Format("{0}\\{1}", FolderName, file.FileName));
                Assert.IsNotNull(localStorageFile);
            }
        }
    }
}
